<?php

/**
 * This file is part of the Codizer package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright   Copyright (c) 2010 Advisa (http://www.advisa.fr)
 * @author      Codizer Core Team <team@codizer.org>
 * @package     Codizer
 */

class f_form_Captcha
{    
	private $randString = '';
	private $stringLength = 10;
	private $imageWidth = false;
	private $imageHeight = false;
	private $background = true;
	private $backgroundColor = array('R'=>255, 'V'=>255, 'B'=>255);
	private $borderColor = array('R'=>226, 'V'=>113, 'B'=>59);
	private $borderWidth = 0;
	private $textColor = array('R'=>0, 'V'=>0, 'B'=>0);
	private $forbiddenChars= array(1,0,'l','0');
	private $font = '';
	private $fontSize = 15;
	private $fromBorder = 10;
	private $type = '';
	private $shadow = false;
	private $shadowColor = array('R'=>128, 'V'=>128, 'B'=>128);
	private $shadowX = 2;
	private $shadowY = 2;
	private $backgroundImage = false;
	private $textAngle = 0;
	private $roundedCorners = false;
	private $roundedCornersRadius = 5;

	/**
	 * Constructor - sets the picture type: PNG, GIF, JPEG
	 *
	 * @param string $type picture type
	 */
	protected function __construct($type)
    {
		$this->setImageType($type);
        return $this;
	}

	/**
	 * Get singleton instance
	 *
	 * @return f_form_Captcha
	 */
	public static function getInstance($type = "PNG")
	{
		return new self($type);
	}

	/**
	 * Sets the length of random string generated
	 *
	 * @param int $length chain length
	 */
	public function setStringLength($length)
    {
		$this->stringLength = $length;
        return $this;
	}

	/**
	 * Sets the background color
	 *
	 * @param int $R red
	 * @param int $V green
	 * @param int $B blue
	 */
	public function setBackgroundColor($R,$V,$B)
    {
		$this->backgroundColor['R'] = $R;
		$this->backgroundColor['V'] = $V;
		$this->backgroundColor['B'] = $B;
        return $this;
	}

	/**
	 * Sets the border color
	 *
	 * @param int $R red
	 * @param int $V green
	 * @param int $B blue
	 */
	public function setBorderColor($R,$V,$B)
    {
		$this->borderColor['R'] = $R;
		$this->borderColor['V'] = $V;
		$this->borderColor['B'] = $B;
        return $this;
	}

	/**
	 * Sets the border size
	 *
	 * @param int $width border size in pixel
	 */
	public function setBorderWidth($width)
    {
		$this->borderWidth = (int)$width;
        return $this;
	}

	/**
	 * Sets the text color
	 *
	 * @param int $R red
	 * @param int $V green
	 * @param int $B blue
	 */
	public function setTextColor($R,$V,$B)
    {
		$this->textColor['R'] = $R;
		$this->textColor['V'] = $V;
		$this->textColor['B'] = $B;
        return $this;
	}

	/**
	 * Sets the picture width
	 *
	 * @param int $width picture width in pixel
	 */
	public function setImageWidth($width)
    {
		$this->imageWidth = $width;
        return $this;
	}

	/**
	 * 	Sets the picture height
	 *
	 * @param int $height picture height in pixel
	 */
	public function setImageHeight($height)
    {
		$this->imageHeight = $height;
        return $this;
	}

	/**
	 * 	Set the True Type font and size
	 *
	 * @param string $font font path
	 * @param int $size font size
	 */
	public function setFont($font, $size)
    {
		if(!is_readable($font))
        {
			throw new Exception('The font is not be found');
		}
		$this->font = $font;
		$this->fontSize = $size;
        return $this;
	}

	/**
	 * 	Sets if shadow should be applied to text
	 *
	 * @param int $x offset of the shadow
	 * @param int $y offset of the shadow
	 */
	public function setShadow($x=false,$y=false)
    {
		$this->shadow = true;
		if($x)
        {
			$this->shadowX = (int)$x;
		}

		if($y)
        {
			$this->shadowY = (int)$y;
		}

        return $this;
	}

	/**
	 * Set the shodow color
	 *
	 * @param int $R red
	 * @param int $V green
	 * @param int $B blue
	 */
	public function setShadowColor($R,$V,$B)
    {
		$this->shadow = true;
		$this->shadowColor['R'] = $R;
		$this->shadowColor['V'] = $V;
		$this->shadowColor['B'] = $B;

        return $this;
	}


	/**
	 * 	Defined if a background image must be applied
	 *
	 * @param string $image picture path
	 */
	public function setBackgroundImage($image)
    {
		if(!is_readable($image))
        {
			throw new Exception('The background is not found');
		}

		$this->backgroundImage = $image;
        return $this;
	}

	/**
	 * Set the text angle
	 *
	 * @param int $angle angle in degrees
	 */
	public function setTextAngle($angle)
    {
		$this->textAngle = (int)$angle;
        return $this;
	}

	/**
	 * Sets the size of the margin
	 *
	 * @param int $margin size in pixel of the margin
	 */
	public function setMarginFromBorder($margin)
    {
		$this->fromBorder = (int)$margin;
        return $this;
	}

	public function setRoundedCorners($radius=false)
    {
		$this->roundedCorners = true;
		if($radius)
        {
			$this->roundedCornersRadius = (int)$radius;
		}
        return $this;
	}

	/**
	 * Build the picture
	 *
	 */
	public function getImage()
    {
		if(!$this->font)
        {
			throw new Exception('You must have a font');
		}

		$text = $this->getRandString();
		$text = trim(preg_replace('`(\w)`', '$1  ', $text));
		$box = imagettfbbox($this->fontSize,$this->textAngle,$this->font,$text);

		if(!$this->imageHeight)
        {
			$boxHeight = max($box[1],$box[3]) - min($box[7],$box[5]);
			$this->imageHeight = $boxHeight + $this->borderWidth*2 + $this->fromBorder*2;
		}

		if(!$this->imageWidth)
        {
			$boxWidth = max($box[4],$box[2]) - min($box[6],$box[0]);
			$this->imageWidth =  $boxWidth + $this->borderWidth*2 + $this->fromBorder*2;
		}

		if(function_exists('imagecreatetruecolor'))
        {
			$im = imagecreatetruecolor($this->imageWidth, $this->imageHeight);
		}
        else
        {
			$im = imagecreate($this->imageWidth, $this->imageHeight);
		}

		// border
		if($this->borderWidth > 0)
        {
			$border = imagecolorallocate(
			$im,
			$this->borderColor['R'],
			$this->borderColor['V'],
			$this->borderColor['B']
			);
			if(!$this->roundedCorners)
            {
				imagefilledrectangle(
				$im,
				0,
				0,
				$this->imageWidth,
				$this->imageHeight,
				$border
				);
			}
            else
            {
				$this->ImageRectangleWithRoundedCorners(
				$im,
				0,
				0,
				$this->imageWidth,
				$this->imageHeight,
				$border,
				$this->roundedCornersRadius
				);
			}
		}

		// background
		$background = imagecolorallocate(
		$im,
		$this->backgroundColor['R'],
		$this->backgroundColor['V'],
		$this->backgroundColor['B']
		);
		imagefilledrectangle(
		$im,
		$this->borderWidth,
		$this->borderWidth,
		$this->imageWidth-$this->borderWidth,
		$this->imageHeight-$this->borderWidth,
		$background
		);

		if($this->backgroundImage)
        {
			// Calcul des nouvelles dimensions
			list($width, $height,$type) = getimagesize($this->backgroundImage);

			$new_width = $this->imageWidth-$this->borderWidth*2;
			$new_height = $this->imageHeight-$this->borderWidth*2;

			if($type === 1)
            {
				$type_ = 'gif';
			}
            elseif($type === 2)
            {
				$type_ = 'jpeg';
			}
            elseif($type === 3)
            {
				$type_ = 'png';
			}
            else
            {
				throw new Exception('Wrong format for the picture');
			}
			$fct = 'imagecreatefrom' . $type_;
			$imb = $fct($this->backgroundImage);


			imagecopyresampled(
			$im,
			$imb,
			$this->borderWidth,
			$this->borderWidth,
			0,
			0,
			$new_width,
			$new_height,
			$width,
			$height
			);

			imagedestroy($imb);
		}

		// text color
		$textColor = imagecolorallocate (
            $im,
            $this->textColor['R'],
            $this->textColor['V'],
            $this->textColor['B']
		);

		// horizontal centering
		$x = ($this->imageWidth - $boxWidth)/2;
		// vertical centering
		$y = $this->imageHeight   - $this->borderWidth - $this->fromBorder;

		// Shadow
		if($this->shadow)
        {
			$shadow = imagecolorallocate(
                $im,
                $this->shadowColor['R'],
                $this->shadowColor['V'],
                $this->shadowColor['B']
			);
			imagettftext(
                $im,
                $this->fontSize,
                $this->textAngle,
                $x+$this->shadowX,
                $y+$this->shadowY,
                $shadow,
                $this->font,
                $text
			);
		}

		// Text
		imagettftext(
            $im,
            $this->fontSize,
            $this->textAngle,
            $x,
            $y,
            $textColor,
            $this->font,
            $text
		);

		$this->makeHeaders();
		$image_function = 'image' . $this->type;
		$image_function($im, $file);
		imagedestroy($im);
	}


	/**
	 * Get the random string generated
	 *
	 * @return string random string generated
	 */
	public function getRandString()
    {
		if(!$this->randString)
        {
			$T = array_merge(range('a','z') , range('A', 'Z') , range(1,9));
			shuffle($T);
			//$TT = array_filter($T, array($this, 'forbiddenCharsFilter'));
			$TT = array_chunk($T, $this->stringLength);

			$this->randString = implode('', $TT[0]);
		}
		return $this->randString;
	}

	private function ImageRectangleWithRoundedCorners(&$im, $x1, $y1, $x2, $y2, $color, $radius)
    {
		// transparence
		$trans = imageColorAllocate ($im, 255, 255, 255);
		$color_ = imagecolortransparent($im, $trans);
		// rectangle sans coins
		imagefilledrectangle($im, $x1, $y1, $x2, $y2, $color_);
		imagefilledrectangle($im, $x1+$radius, $y1, $x2-$radius, $y2, $color);
		imagefilledrectangle($im, $x1, $y1+$radius, $x2, $y2-$radius, $color);
		// coins arrondis
		imagefilledellipse($im, $x1+$radius, $y1+$radius, $radius*2, $radius*2, $color);
		imagefilledellipse($im, $x2-$radius, $y1+$radius, $radius*2, $radius*2, $color);
		imagefilledellipse($im, $x1+$radius, $y2-$radius, $radius*2, $radius*2, $color);
		imagefilledellipse($im, $x2-$radius, $y2-$radius, $radius*2, $radius*2, $color);

	}

	private function forbiddenCharsFilter($in)
    {
		return in_array($in, $this->forbiddenChars);
	}

	private function setImageType($type)
    {
        $type = strtolower($type);

		switch($type)
        {
			case 'gif' :
			case 'png' :
			case 'jpeg' :
                $this->type = $type;
			break;

			case 'jpg' :
                $this->type = 'jpeg';
			break;

			default :
                $this->type = 'png';
		}

		if(!function_exists('image'.$this->type))
        {
			throw new Exception('La fonction n\'est pas disponible');
		}
	}

	private function makeHeaders()
    {
		header('Expires: Mon, 01 Jan 2000 00:00:00 GMT');
		header('Last-Modified: ' . gmdate("D, d M Y H:i:s") . ' GMT');
		header('Cache-Control: no-store, no-cache, must-revalidate');
		header('Cache-Control: post-check=0, pre-check=0', false);
		header('Pragma: no-cache');
		header('Content-Type: image/' . $this->type);
	}
}